import os
import platform
import time
from math import sin, pi
from uuid import uuid4

import pyautogui
from PIL import ImageGrab
from screeninfo import get_monitors

if platform.system() == "Darwin":
    import Quartz  # For macOS screen info


class PyAutoGUIController:
    def __init__(self, selected_screen=0, is_scaling=True, pause_time=1, show_focus=False):
        self.selected_screen = selected_screen
        self.is_scaling = is_scaling
        self.width, self.height = self.get_screen_size()
        self.offset_x = 0
        self.offset_y = 0
        self.target_dimension = None
        self.set_screen_offset()
        self.show_focus = show_focus
        pyautogui.PAUSE = pause_time
        pyautogui.FAILSAFE = False

    def set_screen_offset(self):
        system = platform.system()
        if system == "Windows":
            screens = get_monitors()
            sorted_screens = sorted(screens, key=lambda s: s.x)
            if self.selected_screen < 0 or self.selected_screen >= len(screens):
                raise IndexError("Invalid screen index.")
            screen = sorted_screens[self.selected_screen]
            self.offset_x = screen.x
            self.offset_y = screen.y
        elif system == "Darwin":
            active_displays = Quartz.CGGetActiveDisplayList(32, None, None)[1]
            screens = []
            for display_id in active_displays:
                bounds = Quartz.CGDisplayBounds(display_id)
                screens.append({
                    'x': int(bounds.origin.x),
                    'y': int(bounds.origin.y),
                    'width': int(bounds.size.width),
                    'height': int(bounds.size.height),
                    'is_primary': Quartz.CGDisplayIsMain(display_id)
                })
            sorted_screens = sorted(screens, key=lambda s: s['x'])
            if self.selected_screen < 0 or self.selected_screen >= len(screens):
                raise IndexError("Invalid screen index.")
            screen = sorted_screens[self.selected_screen]
            self.offset_x = screen['x']
            self.offset_y = screen['y']
        else:
            # Linux or other OS
            self.offset_x = 0
            self.offset_y = 0

    def get_screen_size(self):
        system = platform.system()
        if system == "Windows":
            screens = get_monitors()
            sorted_screens = sorted(screens, key=lambda s: s.x)
            if self.selected_screen < 0 or self.selected_screen >= len(screens):
                raise IndexError("Invalid screen index.")
            screen = sorted_screens[self.selected_screen]
            return screen.width, screen.height
        elif system == "Darwin":
            active_displays = Quartz.CGGetActiveDisplayList(32, None, None)[1]
            screens = []
            for display_id in active_displays:
                bounds = Quartz.CGDisplayBounds(display_id)
                screens.append({
                    'width': int(bounds.size.width),
                    'height': int(bounds.size.height)
                })
            sorted_screens = sorted(screens, key=lambda s: s['width'])
            if self.selected_screen < 0 or self.selected_screen >= len(screens):
                raise IndexError("Invalid screen index.")
            screen = sorted_screens[self.selected_screen]
            return screen['width'], screen['height']
        else:
            # Linux or other OS
            cmd = "xrandr | grep ' primary' | awk '{print $4}'"
            output = os.popen(cmd).read()
            resolution = output.strip().split()[0]
            width, height = map(int, resolution.split('x'))
            return width, height

    def scale_coordinates(self, source, x, y):
        if not self.is_scaling:
            return x, y
        ratio = self.width / self.height
        target_dimension = None
        max_scaling_targets = {
            "XGA": {"width": 1024, "height": 768},
            "WXGA": {"width": 1280, "height": 800},
            "FWXGA": {"width": 1366, "height": 768},
            "FHD": {"width": 1920, "height": 1080},
            "HD+": {"width": 1720, "height": 720}
        }
        for target_name, dimension in max_scaling_targets.items():
            if abs(dimension["width"] / dimension["height"] - ratio) < 0.02:
                if dimension["width"] < self.width:
                    target_dimension = dimension
                    self.target_dimension = target_dimension
                    break
        if target_dimension is None:
            target_dimension = max_scaling_targets["WXGA"]
            self.target_dimension = target_dimension
        x_scaling_factor = target_dimension["width"] / self.width
        y_scaling_factor = target_dimension["height"] / self.height
        if source == "API":
            return round(x / x_scaling_factor), round(y / y_scaling_factor)
        else:
            return round(x * x_scaling_factor), round(y * y_scaling_factor)

    async def screenshot(self):
        output_dir = "./tmp/outputs"
        os.makedirs(output_dir, exist_ok=True)
        path = os.path.join(output_dir, f"screenshot_{uuid4().hex}.png")
        system = platform.system()
        if system == "Windows":
            screens = get_monitors()
            sorted_screens = sorted(screens, key=lambda s: s.x)
            screen = sorted_screens[self.selected_screen]
            bbox = (screen.x, screen.y, screen.x + screen.width, screen.y + screen.height)
        elif system == "Darwin":
            active_displays = Quartz.CGGetActiveDisplayList(32, None, None)[1]
            screens = []
            for display_id in active_displays:
                bounds = Quartz.CGDisplayBounds(display_id)
                screens.append({
                    'x': int(bounds.origin.x),
                    'y': int(bounds.origin.y),
                    'width': int(bounds.size.width),
                    'height': int(bounds.size.height)
                })
            sorted_screens = sorted(screens, key=lambda s: s['x'])
            screen = sorted_screens[self.selected_screen]
            bbox = (screen['x'], screen['y'], screen['x'] + screen['width'], screen['y'] + screen['height'])
        else:
            # Linux or other OS
            bbox = (0, 0, self.width, self.height)
        screenshot = ImageGrab.grab(bbox=bbox)
        if self.target_dimension:
            screenshot = screenshot.resize((self.target_dimension["width"], self.target_dimension["height"]))
        screenshot.save(path)
        if os.path.exists(path):
            return path
            # return base64.b64encode(open(path, 'rb').read()).decode()
        else:
            raise RuntimeError(f"Failed to take screenshot: {path} does not exist.")

    def shake_mouse(self, duration=1, amplitude=100):

        """
        模拟晃动鼠标来突出显示鼠标位置

        参数:
            duration: 晃动持续时间(秒)
            amplitude: 晃动幅度(像素)
        """
        # 保存初始鼠标位置
        start_x, start_y = pyautogui.position()

        # 设置移动的平滑度
        steps = 50
        start_time = time.time()

        while (time.time() - start_time) < duration:
            # 计算当前时间点的位置
            progress = (time.time() - start_time) / duration

            # 使用正弦函数创建衰减的摆动效果
            decay = 1 - progress  # 随时间衰减
            offset_x = amplitude * sin(progress * 8 * pi) * decay
            offset_y = amplitude * sin(progress * 8 * pi + pi / 2) * decay  # 添加相位差使运动更自然

            # 移动鼠标到新位置
            pyautogui.moveTo(
                start_x + offset_x,
                start_y + offset_y,
                duration=1 / steps,  # 使移动更平滑
                _pause=False  # 避免每次移动都暂停
            )

        # 最后回到起始位置
        pyautogui.moveTo(start_x, start_y, duration=0.1)

    def force_mouse(self):
        system = platform.system()
        print(system)
        if system == "Windows":
            key = "ctrl"
            pyautogui.keyDown(key)
            pyautogui.keyUp(key)
            time.sleep(0.1)
            pyautogui.keyDown(key)
            pyautogui.keyUp(key)
        else:
            self.shake_mouse()

    async def __call__(self, action, text=None, coordinate=None):
        if action in ("mouse_move", "left_click_drag"):
            if coordinate is None:
                raise ValueError(f"coordinate is required for {action}")
            x, y = self.scale_coordinates("API", coordinate[0], coordinate[1])
            x += self.offset_x
            y += self.offset_y
            if action == "mouse_move":
                pyautogui.moveTo(x, y)
                if self.show_focus:
                    self.force_mouse()
                return f"Moved mouse to ({x}, {y})"
            elif action == "left_click_drag":
                pyautogui.dragTo(x, y, duration=0.5)
                return f"Dragged mouse to ({x}, {y})"
        elif action in ("key", "type"):
            if text is None:
                raise ValueError(f"text is required for {action}")
            if action == "key":
                keys = text.split('+')
                for key in keys:
                    pyautogui.keyDown(key.strip().lower())
                for key in reversed(keys):
                    pyautogui.keyUp(key.strip().lower())
                return f"Pressed keys: {text}"
            elif action == "type":
                pyautogui.typewrite(text, interval=0.012)
                return text
        elif action in ("left_click", "right_click", "double_click", "middle_click"):
            if action == "left_click":
                pyautogui.click()
            elif action == "right_click":
                pyautogui.rightClick()
            elif action == "double_click":
                pyautogui.doubleClick()
            elif action == "middle_click":
                pyautogui.middleClick()
            return f"Performed {action}"
        elif action == "screenshot":
            return await self.screenshot()
        else:
            raise ValueError(f"Invalid action: {action}")
